package proyectosdservidor;

import java.awt.List;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Hashtable;

public class Server extends Thread{

    private int serverID; // numero de 0 hasta (cantidad de servidores)
    private int port;
    private List consola;
    private Registros registros;    // Almacen de datos.
    private DatagramSocket _socket = null;
    boolean stop = false;
    
    private LH lh;
    private int N = 1;  // Cantidad de servidores que conoce.
    private Hashtable otherServers = new Hashtable();  // Los otros Servidores que conoce.


    public Server(int port, List consola, int serverID) throws Exception{
        this.port = port;
        this.consola = consola;
        this.registros = new Registros();
        this.serverID = serverID;
        this._socket = new DatagramSocket(port);
        this.lh = new LH();
    }


    @Override
    public void run(){
        consola.add("Escuchando en puerto: " + port);
        do{
            try{
                DatagramPacket packet = new DatagramPacket(new byte[255],255);
                _socket.receive(packet);
                ProyectoSDServidorView.obs_servidores.add("Se envió un paquete al servidor S"
                        + serverID + " desde el puerto " + packet.getPort());
                consola.add("<Paquete entrante>");

                // Procesamiento
                byte[] data = packet.getData();
                String msg = new String(data);
                msg = msg.trim();
                String[] datos = msg.split(";");
                int op = Integer.parseInt(datos[0]);
                
                String[] registro = datos[1].split(":");
                String id = registro[0];
                String info;
                int cPort;
                
                switch(op){
                    case 1:
                        // Insertar
                        info = registro[1];
                        cPort = Integer.parseInt(datos[2].trim());
                        insertar_datos(id, info, cPort); 
                        break;
                    case 2:
                        // Recuperar
                        cPort = Integer.parseInt(datos[2].trim());
                        recuperar_datos(id, cPort);
                        break;
                    case 3:
                        // Modificar
                        info = registro[1];
                        modificar_datos(id, info);
                        break;
                    case 4:
                        // Eliminar
                        eliminar_datos(id);
                        break;
                    case 5:
                        // Dividir
                        info = registro[1];
                        cPort = Integer.parseInt(datos[2].trim());
                        dividirBalde(Integer.parseInt(id), Integer.parseInt(info), cPort);
                        break;
                    default:
                        stop = true; // Si el coordinador envía "Stop" se detiene.
                        break;
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }while(!stop);
    }

    public void incN(){
        this.N++;
    }

    public void stopServer(){
        this.stop = true;
    }

    public int getServerPort(){
        return this.port;
    }

    public int getServerID(){
        return this.serverID;
    }

    public boolean isOverFlow(){
        return registros.isOverFlow();
    }

    private void insertar_datos(String RID, String registro, int cPort){
        Integer servIdDestino = lh.calcularLH(RID, this.N);
        if(servIdDestino == this.serverID)
        {
            // EL REGISTRO ES PARA ESTE SERVIDOR, LO GUARDO.
            boolean inserto = this.registros.insertar(RID, registro);
            if(inserto) {
                // ACK al cliente
                sendMessage("1;" + this.serverID + ":" + this.port, cPort);
                consola.add("Registro: " + RID + " agregado exitosamente");
            } else{
                consola.add("Registro: " + RID + " existe previamente");
            }

            /**
             * PF: PA MI ESTE IF DE ABAJO DEBERÍA ABARCAR, EN UN ELSE, TODO LO ANTERIOR.
             * LG: NO, PORQUE SINO EL REGISTRO NO SE ALMACENA EN NINGUN LADO SI HAY OVERFLOW
             */
            if (isOverFlow())
                sendMessage(this.serverID + ";"+cPort, 3317);
        } else {
            // No le pertenece a este servidor, redireccionar
            Integer puertoDestino = (Integer)otherServers.get(servIdDestino);
            sendMessage("1;" + RID + ":" + registro + ";" + cPort, puertoDestino);
            consola.add("Registro: " + RID + " direccionado a servidor " + servIdDestino);
        }
    }

    /**
     * DIVISIÓN DEL BALDE.
     * PARA CADA REGISTRO, DE ESTE SERVIDOR, RE-CALCULAMOS EL PROCESO DE INSERCIÓN,
     * PERO AHORA CON OTRO SERVIDOR MÁS ACTIVO.
     * @param portNewServer "puerto del nuevo servidor"
     */
    private void dividirBalde(int idNewServer, int portNewServer, int cPort){

        consola.add("Efectuando división...");
        // Agregamos el nuevo servidor a la lista
        this.otherServers.put(idNewServer, portNewServer);

        // Copiamos la lista de registros en otra lista distinta.
        ArrayList<Registro> oldReg = new ArrayList<Registro>();
        oldReg = this.registros.getRegistros();

        // Limpiamos la lista de registros completamente.
        this.registros.clearMen();
        Integer portAux;

        // Para cada registro averiguamos su nuevo destino, y lo insertamos.
        for(Registro r: oldReg){
            int newDir = lh.calcularLH(r.RID, this.N);
            
            //Si el destino es este mismo servidor, lo inserto nuevamente, en la lista derecho.
            if(newDir == this.serverID){
                insertar_datos(r.RID, r.registro, cPort);
            }else{
                consola.add("El registro "+r.RID+" se envió a "+newDir);
                portAux = (Integer)otherServers.get(newDir);
                sendMessage("1;"+r.RID+":"+r.registro+";"+cPort, portAux);
            }
        }
        consola.add("División completada");
    }

    
    private void recuperar_datos(String RID, int port){
        String reg = registros.recuperar(RID);
        try {
            if(ProyectoSDServidorView.jCheckBox1.isSelected())
                sleep(8000);
            DatagramSocket socket = new DatagramSocket();
            socket.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
            System.out.println("Se responde: " + reg);
            String msg = "2;" + reg;
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            socket.send(packet);
            consola.add("Registro: " + RID + " accedido exitosamente");
            socket.close();
        } catch (Exception ex) {}        
    }
    
    private void modificar_datos(String RID, String newReg){
        registros.modificar(RID, newReg);
        consola.add("Registro: " + RID + " modificado exitosamente");
    }
    
    private void eliminar_datos(String RID){
        registros.eliminar(RID);
        consola.add("Registro: " + RID + " eliminado exitosamente");
    }


    /**
     * Imprime en la consola los registros que tiene.
     */
    public void printRegistros(){
        for(Registro r: registros.getRegistros()){
            consola.add(r.RID + ";" + r.registro);
        }
    }

    private void sendMessage(String msj, int port){
        byte[] buffer = msj.getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        try {
            _socket.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
            _socket.send(packet);
            _socket.disconnect();
        } catch (Exception ex) {}
    }
}
